#include <stdio.h>
#include <string.h>

#include "common.h"
#include "xymodem.h"
#include "command.h"

#define MODEM_DELAY_US 20
#define MODEM_TIMEOUT_US 5000000

#define MODEM_RETRY 40
#define MODEM_CRC_RETRY 50

#define MODEM_CAN_COUNT 3

#define udelay(us)
#define tsts_rx
#define tsts_tx
#define send_byte
#define recv_byte

static cmd_res send_byte_timeout(u8 data)
{
    int retry = MODEM_TIMEOUT_US / MODEM_DELAY_US;
#if 0
    while((OK != tsts_tx()) && retry > 0){
        udelay(MODEM_DELAY_US);
        retry--;
    }
    if(OK == tsts_tx()){
        send_byte(data);
        return OK;
    }
#endif
    return TIME_OUT;  
}

static cmd_res recv_byte_timeout(u8 *data)
{
    int retry = MODEM_TIMEOUT_US / MODEM_DELAY_US;
#if 0
    while((OK != tsts_rx(()) && retry > 0){
        udelay(MODEM_DELAY_US);
        retry--;
    }
    if(OK == tsts_rx()){
        recv_byte(data);
        return OK;
    }
#endif
    return TIME_OUT;  
}

static unsigned int parse_filelen(u8 *pstr)
{
    int datatype =10;
    int result = 0;;

    if(pstr[0] == '0' && (pstr[1] == 'x' || pstr[1] == 'X')){
        datatype = 16;
        pstr += 2;
    }

    while('\0' != *pstr){
        if(*pstr >= '0' && *pstr <= '9'){
            result = (result * datatype) + (*pstr - '0');
        }else{/*hex*/
            if(16 == datatype){
                if(*pstr >= 'A' && *pstr <= 'F'){
                    result = result * 16 + *pstr - 'A' + 10;
                }else{
                    result = result * 16 + *pstr - 'a' + 10;
                }
            }else{
                return result;
            }
        }
        pstr++;
    }
    return result;
}

void parse_header(tmodem *modem,u8 *data)
{
    char *pname;
    int name_len;

    pname = modem->filename;
    name_len = 0;
    while((name_len < FILENAME_LEN) && (*data != '\0')){
        pname[name_len++] = *data++;
    }
    pname[name_len] = '\0';
    /*skip space*/
    while(SPACE == *data++);
    modem->file_len = parse_filelen(data);
}

static void modem_cancle(void)
{
    send_byte_timeout(CAN);
    send_byte_timeout(CAN);
    send_byte_timeout(CAN);
    send_byte_timeout(CAN);
    send_byte_timeout(CAN);
    send_byte_timeout(BSP);
    send_byte_timeout(BSP);
    send_byte_timeout(BSP); 
}

static int modem_recvdata(tmodem *mblock,u8 *databuf)
{
    bool hd_found = FALSE;
    int i;
    int can_counter = 0;;
    int hdr_chars = 0;
    u8 *in_ptr = databuf;
    int cksum;
    u8 ch,blk,cblk,crch,crcl;

    if(mblock->tx_ack){
        send_byte_timeout(ACK);
        mblock->tx_ack = FALSE;
    }

    if(FALSE == mblock->connected){
        send_byte_timeout(mblock->crc_mode ? CRC : NAK);
    }

    while(!hd_found){
        if(OK == recv_byte_timeout(&ch)){
            hdr_chars++;
            switch(ch){
            case SOH:
                hd_found = TRUE;
                mblock->pkt_len = 128;
                break;
            case STX:
                hd_found = TRUE;
                mblock->pkt_len = 1024;
                break;
            case CAN:
                if((++can_counter) >= MODEM_CAN_COUNT){
                    return MODEM_CAN;
                }
                break;
            case EOT:
                if(hdr_chars == 1){
                    return MODEM_EOF;
                }
                break;
            default:
                break;
            }                
         }
         else{
                return MODEM_TIMEOUT;
             }
    }

    if(OK != recv_byte_timeout(&blk)){
        return MODEM_TIMEOUT;
    }

    if(OK != recv_byte_timeout(&cblk)){
        return MODEM_TIMEOUT;
    }
    
    /*receive block data*/
    for(i=0;i<mblock->pkt_len;i++){
        if(OK != recv_byte_timeout(in_ptr++)){
            return MODEM_TIMEOUT;
         }
    }

    if(OK != recv_byte_timeout(&crch)){
        return MODEM_TIMEOUT;
    }

    if(mblock->crc_mode){
        if(OK != recv_byte_timeout(&crcl)){
            return MODEM_TIMEOUT;
        }
    }

    in_ptr = databuf;
    if(mblock->crc_mode){
        //cksum = cyg_crc16(in_ptr,mblock->pkt_len);
        cksum &= 0xFFFF;

        if(cksum != (crch << 8 | crcl)){
            return MODEM_CRC_ERR;
        }
    }else{
        for(i=0;i<mblock->pkt_len;i++){
            cksum += in_ptr[i];
        }
        if((cksum & 0xFF) != crch){
            return MODEM_CRC_ERR;
        }
    }

    mblock->cur_num = blk;
    if(blk != mblock->nxt_num){
        return MODEM_FRAME;
    }

    mblock->nxt_num++;
    mblock->nxt_num &= 0xFF;
    return MODEM_OK;       
}

int modem_read_pkt(tmodem *mblock,u8 *pbuf)
{
    int status;
    int retry;
    int crc_retry;

    retry = MODEM_RETRY;
    crc_retry = MODEM_CRC_RETRY;

    while(retry--){
        status = modem_recvdata(mblock,pbuf);

        if(MODEM_OK == status){
            if(FALSE == mblock->connected){
                mblock->connected = TRUE;
                if(MODEM_TYPE_Y == mblock->type){
                    parse_header(mblock,pbuf);
                    send_byte_timeout(ACK);
                    send_byte_timeout(CRC);
                    return MODEM_CNT;
                }
            }
            mblock->tx_ack = TRUE;
        }else if(MODEM_EOF == status){
            send_byte_timeout(ACK);

            if(mblock->type == MODEM_TYPE_Y){
                mblock->nxt_num = 0;
                mblock->file_cnt++;

                send_byte_timeout(mblock->crc_mode? CRC : NAK);
                modem_recvdata(mblock,pbuf);
                send_byte_timeout(ACK);
            }
            return MODEM_EOF;
        }else if((MODEM_FRAME == status) || (MODEM_CRC_ERR == status)){
            send_byte_timeout(NAK);
        }else if(MODEM_TIMEOUT == status){
            if(FALSE == mblock->connected){
                if(--crc_retry <= 0){
                    mblock->crc_mode = FALSE;
                }
            }
        }else if(MODEM_CAN == status){
            send_byte_timeout(ACK);
            modem_cancle();
            return MODEM_CAN;
        }
    }
    return MODEM_TIMEOUT;
}

void modem_init(tmodem *mblock,mtype type)
{
    mblock->type     = type;
    mblock->crc_mode = TRUE;
    mblock->file_cnt = 0;
    mblock->tx_ack   = FALSE;
    mblock->connected = FALSE;
    mblock->nxt_num  = 0;
    mblock->cur_num  = 0;
    mblock->file_len = 0;
    mblock->rec_err  = 0;

    memset(mblock->filename,0,FILENAME_LEN);
    if(mblock->type == MODEM_TYPE_X){
        mblock->nxt_num = 1;
    }
}

int modem_recv_file(const mtype type,bool ctn,u8 *databuf,int *file_len)
{
    tmodem modem;
    tmodem *pmodem;
    bool complete;
    bool ctn_recv;
    int status;
    int recv_len;
    u8 *pbuf;

    pbuf = databuf;
    pmodem = &modem;

    ctn_recv = FALSE;
    if((TRUE == ctn) && (MODEM_TYPE_Y == type)){
        pmodem->ctn_recv = TRUE;
    }
    pmodem->ctn_recv = ctn_recv;
    *file_len = 0;
    modem_init(pmodem,type);

    while(1){
        recv_len = 0;
        complete = FALSE;

        while(!complete){
            status = modem_read_pkt(pmodem,pbuf);
            switch(status){
                case MODEM_OK:
                {
                    pbuf  += pmodem->pkt_len;
                    recv_len += pmodem->file_len;
                    pmodem->tx_ack = TRUE;
                 }
                 break;
                 case MODEM_EOF:
                 {
                    return MODEM_OK;
                 }
                 break;
                 case MODEM_EOTT:
                 {
                    return MODEM_OK;
                 }
                 break;
                 case MODEM_CNT:
                 {
                    if(MODEM_TYPE_Y == pmodem->type){
                        *file_len = pmodem->file_len;
                    }
                 }
                 break;
                 default:
                    return status;
            }
       }                
    }
}
